昨天已經寫完 season
跟 game_type
的 util
,我們是用字串的陣列來判斷使用者傳入的值正不正確,像是 season
是用年份可能就還算好辨別,但如果看到 game_type
的話,用每個英文的頭一個字當簡寫可能就沒辦法那麼清楚。再來就是這兩個都能直接用一個值代表,但如果是 month
的話,因為他會有兩個月份合在一起的選項,這樣就不能直接用數字代替,可能就要用 key & value
的 dictionary
來儲存代表的數字,像是:
month_map = {
"Mar/Apr": 4,
"May": 5,
# ... 其他月份
}
這樣的話如果要使用指定月份的值,就只要 month_map["Mar/Apr"]
,而如果要檢查是否在可使用的月份內的話就可以用 month in month_map.keys()
。
雖然 dictionary
能達到我們多數的需求,但還是存在一些問題像是容易被修改數值,可能一個不小心就會在哪邊把對應的 key
裡面的 value
改掉。又或是在定義的時候,不小心用到重複的 key
值,會直接覆蓋比較舊的值。
month_map = {
"Mar/Apr": 4,
"May": 5,
# ... 其他月份
}
# 會把原本的 5 改成 "May" 了
month_map["May"] = "May"
# 最後 month_map["May"] 會等於 6,而不是 5
month_map = {
"Mar/Apr": 4,
"May": 5,
"May": 6
# ... 其他月份
}
因此像是月份這種固定的常數,會更傾向於用 Enum 來儲存。
要建立一個 Enum
非常簡單,只要 import
Python 內建套件裡的 enum
,再來使用 class
來宣告就好,用我們的 month
當範例就可以寫成,記得開另外開一個資料夾 enums
儲存:
# src/baseball_stats_python/enums/statcast.py
from enum import Enum
class Month(Enum):
MARCH_AND_APRIL = "4"
MAY = "5"
JUNE = "6"
JULY = "7"
AUGUST = "8"
SEPTEMBER_AND_OCTOBER = "9"
因為常數的關係,會習慣使用全大寫跟 _
來分隔單字,Enum
也不會剛剛提到的 dictionary
的問題,如果我們不小心重複多加了另一個 AUGUST
,系統就會有提示說已經有同樣的 member
了。
建立完一個 Enum
,直接輸入 Month.MARCH_AND_APRIL
就可以使用一個型態為 <enum 'Month'>
的值,裡面可以用 .name
獲得 key
值,然後用 .value
取得裡面的值。
我們之後的判斷都會使用 .value
,不過這樣要每次都要多打很麻煩,就可以多加 [str] 修改預設值(https://docs.python.org/zh-tw/3/library/enum.html#enum.Enum.str):
class Month(Enum):
MARCH_AND_APRIL = "4"
MAY = "5"
JUNE = "6"
JULY = "7"
AUGUST = "8"
SEPTEMBER_AND_OCTOBER = "9"
def __str__(self):
return self.value
這樣在使用 Month.MARCH_AND_APRIL
就會直接得到 4
接下來再來設置檢查使用者輸入的值,是否有在我們設定的 Enum
裡面,這邊會用到 Enum
裡的 _value2member_map_
,以及運用 class
的特性,設定一個 classmethod
,讓我們可以使用 Month.has_value
這樣的方式來檢查,寫法如下:
class Month(Enum):
MARCH_AND_APRIL = "4"
MAY = "5"
JUNE = "6"
JULY = "7"
AUGUST = "8"
SEPTEMBER_AND_OCTOBER = "9"
def __str__(self):
return self.value
@classmethod
def has_value(cls, value):
return value in cls._value2member_map_
有了這些,就能來寫我們的 Util
:
from enums.statcast import Month
def get_month_param_str(month: str | list[str]) -> str:
if (type(month) != str and type(month) != list):
raise ValueError(f"Invalid type for month: {month}")
if (type(month) == list):
if any(not Month.has_value(month) for month in month):
raise ValueError(f"Invalid months: {'|'.join(month)}")
return f"{'|'.join(month)}|"
if (month == ""):
return ""
if (month == "all"):
return f"{'|'.join(month)}|"
if (not Month.has_value(month)):
raise ValueError(f"Invalid month: {month}")
return f"{month}|"
記得要使用到 Enum
的 Month
要從 enums
資料夾 import
,接著就可以用 Month.has_value
來檢查是否合法,剩下都差不多,他跟 game_type
一樣最後要加 |
,然後因為 month
預設是空字串,空字串就不用最後加 |
,也要另外處理。
今天算簡單介紹了一下 Enum
的用法,他其實可以做其他很多延伸,大家可以去試著玩玩看,pybaseball 也有用到,也可以參考看看。有了 Enum
剩下的 params util
都可以順利完成,明天應該就會來測試上傳到 PyPI
有沒有問題,篇幅夠的話會來寫 README
文件。
最後一樣感謝大家耐心地看完這篇文章,有任何問題或建議都歡迎留言告訴我,明天見,掰掰。